package frontend;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class Lexer {
    private static Lexer lexer;
    private final ErrorList errorList = ErrorList.getInstance();
    private final HashMap<String,String> retainers = new HashMap<>();
    private final HashMap<String,String> operators = new HashMap<>();
    public static final HashMap<String,String> others = new HashMap<>();
    private static final ArrayList<Token> tokens = new ArrayList<>();
    private int signal = 0;
    private static class STATUS{
        static final int CENTRAL = 0;
        static final int DIGIT = 1;
        static final int WRONGVAR = 2;
        static final int CORRECTVAR = 3;
        static final int WAITEQUAL = 10;
        static final int DOUBLE = 15;
        static final int SINGLE = 17;
        static final int DIV = 20;
        static final int COMMENTS = 21;
        static final int AND = 25;
        static final int OR = 26;
    }
    public static Lexer getInstance() {
        if(lexer == null) {
            lexer = new Lexer();
        }
        return lexer;
    }

    private Lexer() {
        //for reserved
        retainers.put("main","MAINTK");
        retainers.put("const","CONSTTK");
        retainers.put("int","INTTK");
        retainers.put("char","CHARTK");
        retainers.put("break","BREAKTK");
        retainers.put("continue","CONTINUETK");
        retainers.put("if","IFTK");
        retainers.put("else","ELSETK");
        retainers.put("for","FORTK");
        retainers.put("getint","GETINTTK");
        retainers.put("getchar","GETCHARTK");
        retainers.put("printf","PRINTFTK");
        retainers.put("return","RETURNTK");
        retainers.put("void","VOIDTK");
        //for operator
        operators.put("!","NOT");
        operators.put("&&","AND");
        operators.put("||","OR");
        operators.put("+","PLUS");
        operators.put("-","MINU");
        operators.put("*","MULT");
        operators.put("/","DIV");
        operators.put("%","MOD");
        operators.put("<","LSS");
        operators.put("<=","LEQ");
        operators.put(">","GRE");
        operators.put(">=","GEQ");
        operators.put("==","EQL");
        operators.put("!=","NEQ");
        operators.put("=","ASSIGN");
        //for others
        others.put(";","SEMICN");
        others.put(",","COMMA");
        others.put("(","LPARENT");
        others.put(")","RPARENT");
        others.put("[","LBRACK");
        others.put("]","RBRACK");
        others.put("{","LBRACE");
        others.put("}","RBRACE");
    }
    public void getTokens(List<String> lines){
        int length = lines.size();
        for (int i = 0;i < length;i++) {
            String line = lines.get(i);
            if (signal == 1) {
                getToken(line,21, i + 1);
            } else {
                getToken(line, 0, i + 1);
            }
        }
    }

    private void getToken(String line,int state,int depth) {
        StringBuilder temp = new StringBuilder();
        int len = line.length();
        int status = state;
        for (int i = 0;i < len;i++) {
            char nowAt = line.charAt(i);
            String nowAtStr = String.valueOf(nowAt);
            switch (status) {
                case STATUS.CENTRAL:
                    if (Character.isWhitespace(nowAt)) {
                        status = STATUS.CENTRAL;
                    } else if(Character.isDigit(nowAt)) {
                        temp.append(nowAt);//const digit
                        status = STATUS.DIGIT;
                    } else if(Character.isLetter(nowAt)||nowAt == '_') {
                        temp.append(nowAt);//var
                        status = STATUS.CORRECTVAR;
                    } else if(others.containsKey(nowAtStr)) {
                        Token token = new Token(nowAtStr,others.get(nowAtStr),depth);
                        tokens.add(token);//single other signal
                    } else if (isOperator(nowAt)) {
                        Token token = new Token(nowAtStr,operators.get(nowAtStr),depth);
                        tokens.add(token);//single operator,not contains '/'
                    } else if (isWaiter(nowAt)) {
                        temp.append(nowAt);//some signals waiting for '='
                        status = STATUS.WAITEQUAL;
                    } else if (nowAt == '\"') {
                        temp.append(nowAt);/*step into the status ignore the
                        content inside the quotation marks.*/
                        status = STATUS.DOUBLE;
                    } else if (nowAt == '\'') {
                        temp.append(nowAt);/*step into the status ignore the
                        content inside the quotation marks.*/
                        status = STATUS.SINGLE;
                    } else if (nowAt == '/') {//Perhaps it's a division symbol, or maybe it's a comment
                        status = STATUS.DIV;
                    } else if (nowAt == '&') {
                        status = STATUS.AND;
                    } else if (nowAt == '|') {
                        status = STATUS.OR;
                    } else {
                        error(2,depth);
                    }
                    break;
                case STATUS.DIGIT://for digit
                    if (Character.isDigit(nowAt)) {
                        temp.append(nowAt);
                    } else if (Character.isLetter(nowAt) || nowAt == '_'){
                        temp.append(nowAt);
                        status = STATUS.WRONGVAR;
                    } else {
                        status = STATUS.CENTRAL;
                        Token token = new Token(String.valueOf(temp),"INTCON",depth);
                        tokens.add(token);
                        //System.out.println("i have get a number");
                        temp = new StringBuilder();
                        i--;
                    }
                    break;
                case STATUS.WRONGVAR://for wrong var
                    if (Character.isLetter(nowAt) || nowAt == '_' || Character.isDigit(nowAt) ){
                        temp.append(nowAt);
                    } else {
                        status = STATUS.CENTRAL;
                        String out = String.valueOf(temp);
                        Token token = new Token(out,retainers.get(out),depth);
                        tokens.add(token);
                        temp = new StringBuilder();
                        i--;
                    }
                    break;
                case STATUS.CORRECTVAR://for correct var
                    if (Character.isLetter(nowAt) || nowAt == '_'|| Character.isDigit(nowAt)){
                        temp.append(nowAt);
                    } else {
                        status = STATUS.CENTRAL;
                        String out = String.valueOf(temp);
                        Token token;
                        if (retainers.containsKey(out)) {
                            token = new Token(out, retainers.get(out), depth);
                        } else {
                            token = new Token(out, "IDENFR", depth);
                        }
                        tokens.add(token);
                        temp = new StringBuilder();
                        //System.out.println("i have get a var");
                        i--;
                    }
                    break;
                case STATUS.WAITEQUAL://for '>' || '<' || '!' || '='
                    if (nowAt == '=') {
                        temp.append(nowAt);
                    } else {
                        i--;
                    }
                    {
                        status = STATUS.CENTRAL;
                        String out = String.valueOf(temp);
                        Token token = new Token(out,operators.get(out),depth);
                        tokens.add(token);
                        temp = new StringBuilder();
                    }
                    break;
                case STATUS.DOUBLE://for str
                        temp.append(nowAt);
                    if (nowAt == '\"') {
                        status = STATUS.CENTRAL;
                        Token token = new Token(String.valueOf(temp),"STRCON",depth);
                        tokens.add(token);
                        temp = new StringBuilder();
                    }
                    break;
                case STATUS.SINGLE://for char
                        temp.append(nowAt);
                    if (nowAt == '\\') {
                        i++;
                        temp.append(line.charAt(i));
                        break;
                    }
                    if (nowAt == '\'') {
                        status = STATUS.CENTRAL;
                        Token token = new Token(String.valueOf(temp),"CHRCON",depth);
                        tokens.add(token);
                        temp = new StringBuilder();
                    }
                    break;
                case STATUS.DIV:
                    if (nowAt == '/') {
                        return;
                    } else if (nowAt == '*') {
                        status = STATUS.COMMENTS;
                        signal = 1;
                    } else {
                        status = STATUS.CENTRAL;
                        Token token = new Token("/",operators.get("/"),depth);
                        tokens.add(token);
                        temp = new StringBuilder();
                        i--;
                    }
                    break;
                case STATUS.COMMENTS:
                    //System.out.println(nowAt + " "+line.charAt(i - 1));
                    if (nowAt == '/' && i!=0 && line.charAt(i - 1) == '*') {
                        status = STATUS.CENTRAL;
                        signal = 0;
                    }
                    break;
                case STATUS.AND:
                    if (nowAt != '&') {
                        i--;
                        error(1,depth);
                    }
                    {
                        status = STATUS.CENTRAL;
                        String out = "&&";
                        Token token = new Token(out,operators.get(out),depth);
                        tokens.add(token);
                        temp = new StringBuilder();
                    }
                    break;
                case STATUS.OR:
                    if (nowAt != '|') {
                        i--;
                        error(1,depth);
                    }
                    {
                        status = STATUS.CENTRAL;
                        String out = "||";
                        Token token = new Token(out,operators.get(out),depth);
                        tokens.add(token);
                        temp = new StringBuilder();
                    }
                    break;
                default:
                    error(3,depth);
            }
        }
        endHandle(status, String.valueOf(temp), depth);//There may be extra characters to handle due to line breaks.
    }

    private void endHandle(int status,String string,int line) {
        if (status == STATUS.DIV) {
            string = "/";
        } else if (status == STATUS.AND) {
            string = "&&";
        } else if (status == STATUS.OR) {
            string = "||";
        }
        if (!string.isEmpty()) {
            Token token;
            switch (status) {
                case STATUS.DIGIT:
                    token = new Token(string,"INTCON",line);
                    tokens.add(token);
                    break;
                case STATUS.WRONGVAR:
                    token = new Token(string,retainers.get(string),line);
                    tokens.add(token);
                    break;
                case STATUS.CORRECTVAR:
                if (retainers.containsKey(string)) {
                    token = new Token(string,retainers.get(string),line);
                    tokens.add(token);
                } else {
                    token = new Token(string,"IDENFR",line);
                    tokens.add(token);
                }
                    break;
                case STATUS.WAITEQUAL,STATUS.DIV,STATUS.AND,STATUS.OR:
                    token = new Token(string,operators.get(string),line);
                    tokens.add(token);
                    break;
                default:
                    break;

            }
        }
    }
    private boolean isOperator(char x) {
        return x == '*' || x == '+' || x =='-' || x == '%';
    }

    private boolean isWaiter(char x) {
        return x == '!' || x == '>' || x == '<' || x == '=';
    }

    public void error(int type,int lineOfNum) {
        switch (type) {
            case 1:
                ErrorInf errorInf = new ErrorInf(lineOfNum,"a");
                errorList.insertError(errorInf);
                break;
            default:
                System.out.println("Something wrong happened at " + lineOfNum + "type is :" +
                        type);
        }
    }

    public void outputWord() {
        int len = tokens.size();
        //System.out.println(len);
        try (FileWriter writer = new FileWriter("lexer.txt")) {
            // 向文件中写入内容
            for (int i = 0;i < len;i++) {
                //System.out.println(type.get(i) + ' ' + words.get(i));
                writer.write(tokens.get(i).toString() + '\n');
            }
        } catch (IOException e) {
            // 捕获并处理可能发生的IO异常
            System.out.println("An error occurred while writing to the file.");
            e.printStackTrace();
        }

    }
    public  ArrayList<Token> getToken() {
        return tokens;
    }
}

